/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.util;

import org.griphyn.vdl.util.*;
import java.util.*;
import java.io.*;

/**
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see java.io.File
 */
public class LockHelper {
  /**
   * The suffix to use for the regular lock file.
   */
  public static final String LOCK_SUFFIX = ".lock";
  
  /**
   * The initial timeout to use when retrying with exponential backoff.
   */
  private long m_timeout;

  /**
   * The number of retries to attempt obtaining a lock.
   */
  private int m_retries;

  /**
   * default ctor.
   */
  public LockHelper()
  {
    this.m_timeout = 250;
    this.m_retries = 10;
  }

  /**
   * ctor.
   */
  public LockHelper( long timeout, int retries )
  {
    this.m_timeout = timeout;
    this.m_retries = retries;
  }

  /**
   * Creates a file-based lock file in an NFS secure fashion. Do not use
   * this function for locking, use {@link #lockFile(String)} instead.
   * One exception is to use this method for test-and-set locking.
   *
   * @param filename is the file to create a lockfile for
   * @return the representation of the lock file, or <code>null</code> 
   * in case of error.
   */
  public File createLock( String filename )
  {
    // create local names from basename
    File result = null;
    File lock = new File( filename + LockHelper.LOCK_SUFFIX );

    // exclusively create new file
    boolean created = false;
    try {
      created = lock.createNewFile();
    } catch ( IOException ioe ) {
      Logging.instance().log( "lock", 0, "while creating lock " + 
			      lock.getPath() + ": " + ioe.getMessage() );
      created = false; // make extra sure
    }

    // if the lock was created, continue
    if ( created ) {
      // postcondition: file was created
      Logging.instance().log( "lock", 2, "created lock " + lock.getPath() );
      LockFileSet.instance().add( lock );
      lock.setLastModified( System.currentTimeMillis() ); // force NFS
      result = lock;
    } else {
      // unable to rename file to lock file: lock exists
      Logging.instance().log( "lock", 1, "lock " + lock.getPath() +
			      " already exists" );
    }

    // may be null
    return result;
  }

  /**
   * Locks a file using an empty lockfile. This method repeatedly retries
   * to lock a file, and gives up after a few seconds. 
   *
   * @param filename is the basename of the file to lock.
   * @return true, if the file was locked successfully, false otherwise.
   */
  public boolean lockFile( String filename )
  {
    long timeout = this.m_timeout;

    // do five retries
    for ( int i=0; i<this.m_retries; ++i ) {
      File lock = this.createLock(filename);
      if ( lock == null ) {
	// lock is busy, try again later
	Logging.instance().log( "lock", 1, "file " + filename + " is busy, " +
				"sleeping " + Long.toString(timeout) + " ms " +
				"(retry " + Integer.toString(i) + ')' );
	try {
	  Thread.sleep(timeout);
	} catch ( InterruptedException ie ) {
	  // ignore, as usual
	}
	timeout <<= 1;
      } else {
	// we got the lock, go ahead, and write our pid into it
	// FIXME: we don't have a pid in Java (in Windows)
	return true;
      }
    }
    
    // postcondition: timeout out after retries
    Logging.instance().log( "default", 0, "unable to lock " + filename +
			    ", still busy, giving up!" );
    return false;
  }

  /**
   * Releases a lock file from its basename.
   * @param filename is the name of the basename of the file to unlock.
   * The locking extension will be added internally. 
   * @return true, if the lock was removed successfully, false otherwise.
   */
  public boolean unlockFile( String filename )
  {
    File lock = new File( filename + LockHelper.LOCK_SUFFIX );
    LockFileSet.instance().remove( lock );

    boolean result = lock.delete();
    if ( result )
      Logging.instance().log( "lock", 2, "removed lock " + lock.getPath() );
    return result;
  }
}
